package cn.lg.soar.mvc.aspect;

import cn.lg.soar.common.data.msg.I18n;
import cn.lg.soar.common.model.HttpResult;
import cn.lg.soar.common.model.PageResult;
import cn.lg.soar.common.util.AssertUtil;
import cn.lg.soar.common.util.data.DataUtil;
import cn.lg.soar.common.util.data.StringUtil;
import cn.lg.soar.common.util.listener.SpringUtils;
import cn.lg.soar.common.util.reflect.ReflectUtil;
import cn.lg.soar.common.util.signature.SignBase;
import cn.lg.soar.common.util.signature.SignatureUtils;
import cn.lg.soar.core.annotations.SignatureIssue;
import cn.lg.soar.core.annotations.SignatureVerification;
import cn.lg.soar.core.aspect.AopEvaluationContext;
import cn.lg.soar.core.aspect.SpelExpressionUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.web.bind.annotation.RequestBody;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;

/**
 * @Author: luguoxiang
 * @Date: 2021-05-18 15:41
 * @Description: 数据签名与验签 aop
 * 开源项目：https://gitee.com/lgx1992/lg-soar 求star！请给我star！请帮我点个star！
 */
@Aspect
public class SignatureAspect {

    protected static final Logger LOGGER = LoggerFactory.getLogger(SignatureAspect.class);

    @Autowired
    private ApplicationContext applicationContext;

    private final static Map<String, String> SECRET_CACHE = new ConcurrentReferenceHashMap<>();
    private final static Map<SignatureVerification, Integer> VERIFICATION_CACHE = new ConcurrentReferenceHashMap<>();

    String getSecret(String str) {
        String secret = SECRET_CACHE.get(str);
        if (secret == null) {
            synchronized (SECRET_CACHE) {
                secret = SECRET_CACHE.get(str);
                if (secret == null) {
                    int last = str.indexOf("}");
                    String def = str;
                    if (last == str.length() - 1 && str.indexOf("${") == 0 ) {
                        str = str.substring(2, last);
                        String[] split = str.split(":");
                        if (split.length == 2) {
                            def = split[1];
                        }
                        str = split[0];
                    }

                    secret = SpringUtils.getProperty(str, String.class);
                    if (secret == null) {
                        secret = def;
                    }
                    SECRET_CACHE.put(str, secret);
                }
            }
        }
        return secret;
    }

    static Object getData(JoinPoint point, SignatureVerification signatureVerification) {
        Integer index = VERIFICATION_CACHE.get(signatureVerification);
        Object[] args = point.getArgs();
        if (index == null) {
            synchronized (VERIFICATION_CACHE) {
                index = VERIFICATION_CACHE.get(signatureVerification);
                if (index == null) {
                    Signature signature = point.getSignature();
                    MethodSignature methodSignature = (MethodSignature)signature;
                    Method method = methodSignature.getMethod();
                    Annotation[][] parameterAnnotations = method.getParameterAnnotations();
                    int i = 0;
                    dd: for (; i < parameterAnnotations.length; i++) {
                        Annotation[] annotations = parameterAnnotations[i];
                        for (Annotation annotation : annotations) {
                            if (annotation instanceof RequestBody) {
                                index = i;
                                VERIFICATION_CACHE.put(signatureVerification, index);
                                return args[index];
                            }
                        }
                    }
                    index = 0;
                    VERIFICATION_CACHE.put(signatureVerification, 0);
                }
            }
        }
        return args[index];
    }


    /**
     * 验签
     * @param data
     * @param signatureVerification
     */
    private void verify(Object data, SignatureVerification signatureVerification) {
        String signFieldName = signatureVerification.signField();
        if (StringUtil.isBlank(signFieldName)) {
            if (data instanceof SignBase) {
                signFieldName = "signValue";
            } else {
                throw new RuntimeException("配置错误，请正确配置“signField”字段");
            }
        }
        Object signValue = ReflectUtil.get(data, signFieldName);
        // 读取密钥
        String secret = getSecret(signatureVerification.secret());
        String sign = SignatureUtils.sign(secret, data, signatureVerification.filterType(), signatureVerification.properties());
        if (!sign.equals(signValue)) {
            LOGGER.warn("验签失败：收到的签名值={}，系统生成签名值={}", signValue, sign);
            AssertUtil.throwException(I18n.build("验签失败"));
        }
    }

    /**
     * 签发
     * @param data
     * @param signatureIssue
     */
    private void signature(Object data, SignatureIssue signatureIssue) {
        String signFieldName = signatureIssue.signField();
        if (StringUtil.isBlank(signFieldName)) {
            if (data instanceof SignBase) {
                signFieldName = "signValue";
            } else {
                throw new RuntimeException("配置错误，请正确配置“signField”字段");
            }
        }
        Field field = ReflectUtil.getField(data.getClass(), signFieldName);
        // 读取密钥
        String secret = getSecret(signatureIssue.secret());
        String sign = SignatureUtils.sign(secret, data, signatureIssue.filterType(), signatureIssue.properties());
        ReflectUtil.set(data, field, sign);
    }

    /**
     * 数据签名
     * @param result
     * @param signatureIssue
     */
    @AfterReturning(pointcut = "@annotation(signatureIssue)", returning = "result")
    public void returning(JoinPoint point, SignatureIssue signatureIssue, Object result) {
        // 读取数据
        if ("".equals(signatureIssue.value())) {
            if (result instanceof HttpResult) {
                result = ((HttpResult<?>) result).getData();
            }
            if (result instanceof PageResult) {
                result = ((PageResult<?>) result).getList();
            }
        } else {
            AopEvaluationContext context = new AopEvaluationContext(point, this.applicationContext, result);
            result = SpelExpressionUtils.getValue(signatureIssue.value(), context);
        }
        if (result == null) {
            return;
        }
        // 批量签名处理
        if (result instanceof Map) {
            ((Map<?, ?>) result).forEach((k, v) -> {
                signature(v, signatureIssue);
            });
            return;
        }
        if (result instanceof Iterable) {
            ((Iterable<?>) result).forEach(x -> {
                signature(x, signatureIssue);
            });
            return;
        }
        if (result.getClass().isArray()) {
            DataUtil.arrayToList(result).forEach(x -> {
                signature(x, signatureIssue);
            });
            return;
        }
        // 单个数据签名处理
        signature(result, signatureIssue);
    }

    /**
     * 数据验签
     * @param signatureVerification
     * @param point
     */
    @Before(value = "@annotation(signatureVerification)")
    public void before(JoinPoint point, SignatureVerification signatureVerification) {
        // 获取接收到的值
        Object data;
        if ("".equals(signatureVerification.value())) {
            data = getData(point, signatureVerification);
        } else {
            AopEvaluationContext context = new AopEvaluationContext(point, this.applicationContext);
            data = SpelExpressionUtils.getValue(signatureVerification.value(), context);
        }
        if (data == null) {
            LOGGER.warn("验签失败：数据为空");
            AssertUtil.throwException(I18n.build("验签失败"));
        }
        // 批量签名处理
        if (data instanceof Map) {
            ((Map<?, ?>) data).forEach((k, v) -> {
                verify(v, signatureVerification);
            });
            return;
        }
        if (data instanceof Iterable) {
            ((Iterable<?>) data).forEach(x -> {
                verify(x, signatureVerification);
            });
            return;
        }
        if (data.getClass().isArray()) {
            DataUtil.arrayToList(data).forEach(x -> {
                verify(x, signatureVerification);
            });
            return;
        }
        // 单个数据签名处理
        verify(data, signatureVerification);
    }

}
